package com.rpc.rpcservercontainer;

import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;


class PackageReader {
    private static final Logger log = Logger.getLogger(PackageReader.class);
    
    static List<RpcPackage> readPackages(File appsDirectory) throws IOException
    {
        
        if (appsDirectory == null || ! appsDirectory.isDirectory()) 
        {
            throw new IllegalArgumentException("directory is not a valid input");
        }
        
        log.info("Start reading packages in " + appsDirectory.getAbsolutePath());
        
        Map<String, File> existingDirectoryMap = new HashMap<String, File>();
        Map<String, File> zipFileMap = new HashMap<String, File>();
        
        for (File file : appsDirectory.listFiles())
        {
            if (file.isDirectory()) {
                
                if (log.isDebugEnabled()) {
                    log.debug("Found existing directory: " + file.getName());
                }
                
                existingDirectoryMap.put(file.getName().toLowerCase(), file);
                continue;
            }
            
            String name = file.getName();
            if (name.toLowerCase().endsWith(".zip"))
            {
                int indexOfDotZip = name.toLowerCase().indexOf(".zip");
                String noExtensionName = name.toLowerCase().substring(0,indexOfDotZip);
                
                if (log.isDebugEnabled()) {
                    log.debug("Found app zip file: " + file.getAbsolutePath());
                }
                
                zipFileMap.put(noExtensionName, file);
            }
        }
        
        for (Map.Entry<String, File> zipEntry : zipFileMap.entrySet()) 
        {
            if (existingDirectoryMap.containsKey(zipEntry.getKey())) 
            {
                if (log.isDebugEnabled()) {
                    log.debug("Found directory and zip for the same app '" + zipEntry.getKey() + "; deleting the directory.");
                }
                
                File dir = existingDirectoryMap.get(zipEntry.getKey());
                FileUtils.deleteDirectory(dir);
                existingDirectoryMap.remove(zipEntry.getKey());
            }
            
            File zipFile = zipEntry.getValue();
            int indexOfDotZip = zipFile.getName().toLowerCase().indexOf(".zip");
            String noExtensionName = zipFile.getName().substring(0,indexOfDotZip);
            
            File unzipToFolder = new File(appsDirectory,noExtensionName);
            unzipToFolder.mkdirs();

            if (log.isDebugEnabled()) {
                log.debug("Zip file named: " + zipFile.getName() +" is going to be unzipped into: " + unzipToFolder.getAbsolutePath() );
            }
            
            unzip(zipFile, unzipToFolder); 
            
            existingDirectoryMap.put(unzipToFolder.getName().toLowerCase(), unzipToFolder);
            
            
        }
        
        ImmutableList.Builder<RpcPackage> packages = ImmutableList.builder();
        
        
        for (Map.Entry<String, File> dirEntry : existingDirectoryMap.entrySet())
        {
            File directory = dirEntry.getValue();
            
            File appConfigFile = new File(directory, "application.properties");
            
            if (appConfigFile.exists() == false) 
            {
                throw new IllegalStateException("The app " + directory.getName() + " does not contain an application.properties");
            }
            
            Properties appConfig = new Properties();
            appConfig.load(new FileInputStream(appConfigFile));
            
            RpcPackage pckg = new RpcPackage(directory.getName(), appConfig, directory);
            packages.add(pckg);
            
        }
        
        
        return packages.build();
        
    }
    
    /**
     * Unzips a zip file and returns a list of the extracted file's paths.
     * 
     * @param zipFile file to unzip
     * @return a list of the extracted file's paths.
     */
    private static void unzip(File zipFile, File unzipDestination) {
        File folder = unzipDestination;
        InputStream in = null;
        OutputStream out = null;
        ZipFile zip = null;
        
        try {
            zip = new ZipFile(zipFile);
            Enumeration<? extends ZipEntry> e = zip.entries();
            while(e.hasMoreElements()) {
                try {
                    ZipEntry entry = e.nextElement();
                    
                    if (entry.getName().startsWith("__MACOSX")) {
                        
                        if (log.isDebugEnabled()) {
                            log.debug("The file: " + entry.getName() + " within " + zipFile.getAbsolutePath() + " will be ignored.");
                        }
                        continue;
                    }
                    
                    File file = new File(folder, entry.getName());

                    File fileDir = file.getParentFile();
                    if(!fileDir.exists()) {
                        fileDir.mkdirs();
                    }

                    in = zip.getInputStream(entry);
                    out = new FileOutputStream(file);
                    IOUtils.copy(in, out);

                } finally {
                    IOUtils.closeQuietly(in);
                    IOUtils.closeQuietly(out);
                }
            }
        } catch(Exception ex) {
            throw new RuntimeException("Error occurred while unzipping file", ex);
        } finally {
            try {
                zip.close();
            } catch(IOException ex) { }
        }
    }
    
    static class RpcPackage {
        private final String name;
        private final Properties config;
        private final File packageDirectory;

        private RpcPackage(String name, Properties config, File packageDirectory) {
            this.name = name;
            this.config = config;
            this.packageDirectory = packageDirectory;
        }

        public String getName() {
            return name;
        }

        public Properties getConfig() 
        {
            return config;
        }
        
        ClassLoader createAppClassLoader() 
        {
            
            return null;    
        }
    }
    
    static class AppClassLoader extends ClassLoader
    {
        private AppClassLoader() 
        {
            
        }
    }
}
